#pragma once
#define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING
#include <tbb/tbb.h>
#undef  _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING
#include <array>
#include <memory>
#include "../EntityState/EntityState.h"

namespace LogicGraph
{
	template<typename T>
	struct Pipeline;
	template<typename... Ts>
	struct Pipeline<EntityState::StateManager<Ts...>>
	{
		using StateManager = EntityState::StateManager<Ts...>;
		
		using Trait = typename StateManager::Trait;
		using States = MPL::concat_t<typename Trait::States, typename Trait::GlobalStates>;

		using graph_node = std::shared_ptr<tbb::flow::sender<tbb::flow::continue_msg>>;
		std::array<graph_node, MPL::size<States>{}> out;
		std::array<std::vector<graph_node>, MPL::size<States>{}> in;
		std::vector<graph_node> nodes;

		tbb::flow::graph logic_flow;
		tbb::flow::graph creation_flow;

		using start_node = std::shared_ptr<tbb::flow::broadcast_node<tbb::flow::continue_msg>>;
		start_node start;

		StateManager *states;

		template<typename F>
		Pipeline& operator >> (F&& f)
		{ 
			return combine(std::forward<F>(f));
		}
		
		template<typename T>
		using is_input = std::is_const<std::remove_reference_t<T>>;
		template<typename T>
		using is_output = std::negation<is_input<T>>;

		template<typename F>
		Pipeline& combine(F&& f)
		{
			using FunctionTrait = MPL::generic_function_trait<std::decay_t<F>>;
			using ReturnType = typename FunctionTrait::return_type;
			using ArgumentType = typename FunctionTrait::argument_type;
			using ArgumentsInput = MPL::fliter_t<is_input, ArgumentType>; 
			using InputState = MPL::intersection_t<MPL::map_t<std::decay_t, ArgumentsInput>, typename Trait::AllStates>;
			using ArgumentsOutput = MPL::intersection_t<MPL::map_t<std::decay_t, MPL::fliter_t<is_output, ArgumentType>>, typename Trait::AllStates>;
			using ReturnCreation = std::conditional_t<std::is_same_v<ReturnType, void>, MPL::typelist<>, ReturnType>; 
			using ReturnOutput = MPL::intersection_t<ReturnCreation, typename Trait::States>; 
			using OutputState = MPL::union_t<ReturnOutput, ArgumentsOutput>; 
			
			using logic_node = tbb::flow::continue_node<tbb::flow::continue_msg>;
			std::shared_ptr<logic_node> logic;

			if constexpr(std::is_same_v<ReturnType, void>)
			{
				logic = std::make_shared<logic_node>(logic_flow, [f = std::forward<F>(f), this](tbb::flow::continue_msg)
				{
					states->transit(std::move(f), [](auto&& ff, size_t start, size_t end)
					{
						tbb::parallel_for(start, end, ff, tbb::auto_partitioner{});
					});
				});
			}
			else 
			{
				if constexpr(MPL::contain<typename Trait::Entity, ReturnCreation>::value)
				{
					logic = std::make_shared<logic_node>(creation_flow, [this, f = std::forward<F>(f)](tbb::flow::continue_msg)
					{
						auto pre = std::move(states->grow_hook);
						states->grow_hook = [this] { logic_flow.wait_for_all(); };
						states->transit(std::move(f), [](auto&& f, size_t start, size_t end)
						{
							for (size_t i{ start }; i < end; i++) f(i); 
						});
						states->grow_hook = std::move(pre);
					});
				}
				else
				{
					logic = std::make_shared<logic_node>(logic_flow, [f = std::forward<F>(f), this](tbb::flow::continue_msg)
					{
						states->transit(std::move(f), [](auto&& ff, size_t start, size_t end)
						{
							for (size_t i{ end }; i > start; i--) ff(i - 1);
						});
					});
				}
			}
			nodes.emplace_back(logic);
			MPL::for_types<InputState>([&](auto wrapper)
			{
				using type = typename decltype(wrapper)::type;
				if constexpr(!MPL::contain<type, OutputState>::value) 
				{
					constexpr size_t index = MPL::index<type, States>{};
					tbb::flow::make_edge(*(out[index]), *logic);
					in[index].emplace_back(logic);
				}
			});
			
			MPL::for_types<OutputState>([&](auto wrapper)
			{
				using type = typename decltype(wrapper)::type;
				constexpr size_t index = MPL::index<type, States>{};
				const size_t size = in[index].size();
				for (size_t i{ 0u }; i < size; i++)
					tbb::flow::make_edge(*(in[index][i]), *logic);
				tbb::flow::make_edge(*(out[index]), *logic);
				in[index].clear();
				out[index] = logic;
			});

			return *this;
		}


		Pipeline() :out(), in(), start(new tbb::flow::broadcast_node<tbb::flow::continue_msg>{ logic_flow })
		{
			for (size_t i{ 0u }; i < out.size(); i++)
				out[i] = start;
		}

		void operator()(StateManager& entites) 
		{ 
			states = &entites;
			start->try_put(tbb::flow::continue_msg{});
			logic_flow.wait_for_all();
			creation_flow.wait_for_all();
		}
	};
}
